/*
 * Copyright 2021-2022 Open Kunlun Technology <https://www.openkunlun.io>
 */

package io.openkunlun.scaladsl.server

import akka.grpc.scaladsl.Metadata
import akka.util.Timeout
import com.google.protobuf.ByteString
import io.grpc.internal.GrpcUtil
import io.openkunlun.scaladsl.serialization.DaprObjectSerialization
import io.openkunlun.scaladsl.util.Strings
import io.openkunlun.scaladsl.v1.{ InvokeRequest, InvokeResponse }

import scala.concurrent.duration._
import scala.concurrent.{ ExecutionContext, Future }
import scala.util.Try

/**
 * @author ericxin.
 */
trait InvocationHandler extends DaprHandler {
  val methods: Seq[String]
  def handle(req: InvokeRequest, metadata: Metadata)(implicit ec: ExecutionContext): Future[InvokeResponse]
}
abstract class InvocationService[Req, Resp](implicit m: Manifest[Req]) extends InvocationHandler {

  val deadline: Timeout
  val serialization: DaprObjectSerialization

  /**
   *
   * @param req
   * @param metadata
   * @param ec
   * @return
   */
  override final def handle(req: InvokeRequest, metadata: Metadata)(implicit ec: ExecutionContext): Future[InvokeResponse] = {

    val dataObj = serialization.deserialize[Req](req.data.map(_.value.toByteArray).getOrElse(Array.empty))
    val request = InvocationRequest(req.method, dataObj, Option(req.contentType).filter(Strings.nonEmpty))

    implicit val timeout: Timeout = Try(metadata.getText(GrpcUtil.TIMEOUT).map(_.toLong.nanos).map(Timeout.apply).getOrElse(deadline)).getOrElse(deadline)

    for {
      resp <- handle(request, metadata)
      result = serialization.serialize[Resp](resp.data)
    } yield InvokeResponse(
      Some(com.google.protobuf.any.Any(value = ByteString.copyFrom(result))),
      resp.contentType.filter(Strings.nonEmpty).getOrElse("")
    )
  }

  /**
   *
   * @param req
   * @param metadata
   * @param ec
   * @return
   */
  def handle(req: InvocationRequest[Req], metadata: Metadata)(implicit ec: ExecutionContext, timeout: Timeout): Future[InvocationResponse[Resp]]
}
final case class InvocationRequest[T](method: String, data: T, contentType: Option[String] = None)
final case class InvocationResponse[T](data: T, contentType: Option[String] = None)
